﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace dPet.Application.Services.Implementation
{
	using AutoMapper;
	using dPet.Application.DataObjects;
	using dPet.Application.QueryObjects;
	using dPet.Domain.Models;
	using dPet.Domain.Repositories;
	using dPet.Domain.Services;
	using dPet.Domain.Specifications;
	using dPet.Infrastructure.Logging;
	using Hangerd;
	using Hangerd.Extensions;
	using Hangerd.Repository;

	public class ProductManagementService : ServiceBase, IProductManagementService
	{
		#region Repository

		private readonly IHospitalRepository _hospitalRepository;
		private readonly IProductCategoryRepository _productCategoryRepository;
		private readonly IProductSubCategoryRepository _productSubCategoryRepository;
		private readonly IProductRepository _productRepository;

		#endregion

		#region Services

		private readonly IInvoicingDomainService _invoicingDomainService;

		#endregion

		#region Constructors

		public ProductManagementService(
			IRepositoryContext unitOfWork,
			IHospitalRepository hospitalRepository,
			IProductCategoryRepository productCategoryRepository,
			IProductSubCategoryRepository productSubCategoryRepository,
			IProductRepository productRepository,
			IInvoicingDomainService invoicingDomainService)
			: base(unitOfWork)
		{
			_hospitalRepository = hospitalRepository;
			_productCategoryRepository = productCategoryRepository;
			_productSubCategoryRepository = productSubCategoryRepository;
			_productRepository = productRepository;
			_invoicingDomainService = invoicingDomainService;
		}

		#endregion

		#region Category

		public MessagedResult<bool> AddProductCategory(ProductCategoryDto categoryDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "商品类别管理", "添加大类", (ref ProductCategory category) =>
			{
				var hospital = _hospitalRepository.Get(categoryDto.HospitalId, false);

				if (hospital == null)
				{
					throw new HangerdException("医院信息不存在");
				}

				if (_invoicingDomainService.HasSameCategoryNameUnderHospital(hospital.Id, categoryDto.Name))
				{
					throw new HangerdException("存在同名大类");
				}

				category = new ProductCategory(hospital, categoryDto.Name);

				_productCategoryRepository.Add(category, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public MessagedResult<bool> UpdateProductCategory(string categoryId, ProductCategoryDto categoryDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "商品类别管理", "更新大类", (ref ProductCategory category) =>
			{
				category = _productCategoryRepository.Get(categoryId, true);

				if (category == null)
				{
					throw new HangerdException("大类信息不存在");
				}

				if (_invoicingDomainService.HasSameCategoryNameUnderHospital(category.HospitalId, categoryDto.Name, category))
				{
					throw new HangerdException("存在同名大类");
				}

				category.Name = categoryDto.Name;

				_productCategoryRepository.Update(category, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public IEnumerable<ProductCategoryDto> GetProductCategories(string hospitalId)
		{
			var spec = ProductCategorySpecifications.HospitalIdEquals(hospitalId);

			var categories = _productCategoryRepository.GetAll(spec, false)
				.OrderBy(c => c.CreateTime);

			return Mapper.Map<IEnumerable<ProductCategory>, IEnumerable<ProductCategoryDto>>(categories);
		}

		public ProductCategoryDto GetProductCategory(string id)
		{
			var category = _productCategoryRepository.Get(id, false);

			return Mapper.Map<ProductCategory, ProductCategoryDto>(category);
		}

		#endregion

		#region SubCategory

		public MessagedResult<bool> AddProductSubCategory(ProductSubCategoryDto subCategoryDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "商品类别管理", "添加小类", (ref ProductSubCategory subCategory) =>
			{
				var category = _productCategoryRepository.Get(subCategoryDto.Category.Id, true);

				if (category == null)
				{
					throw new HangerdException("大类信息不存在");
				}

				if (_invoicingDomainService.HasSameSubCategoryNameUnderCategory(category.Id, subCategoryDto.Name))
				{
					throw new HangerdException("该大类下存在同名小类");
				}

				subCategory = new ProductSubCategory(category, subCategoryDto.Name);

				_productSubCategoryRepository.Add(subCategory, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public MessagedResult<bool> UpdateProductSubCategory(string subCategoryId, ProductSubCategoryDto subCategoryDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "商品类别管理", "更新小类", (ref ProductSubCategory subCategory) =>
			{
				subCategory = _productSubCategoryRepository.Get(subCategoryId, true, sc => sc.Category);

				if (subCategory == null)
				{
					throw new HangerdException("小类信息不存在");
				}

				if (_invoicingDomainService.HasSameSubCategoryNameUnderCategory(subCategory.Category.Id, subCategoryDto.Name, subCategory))
				{
					throw new HangerdException("该大类下存在同名小类");
				}

				subCategory.Name = subCategoryDto.Name;

				_productSubCategoryRepository.Update(subCategory, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public IEnumerable<ProductSubCategoryDto> GetProductSubCategories(string categoryId)
		{
			var spec = SubCategorySpecifications.CategoryIdEquals(categoryId);

			var subCategories = _productSubCategoryRepository.GetAll(spec, false)
				.OrderBy(c => c.CreateTime);

			return Mapper.Map<IEnumerable<ProductSubCategory>, IEnumerable<ProductSubCategoryDto>>(subCategories);
		}

		public ProductSubCategoryDto GetProductSubCategory(string id, bool loadCategory)
		{
			var eagerLoadingProperties = new List<Expression<Func<ProductSubCategory, dynamic>>>();

			if (loadCategory)
			{
				eagerLoadingProperties.Add(sc => sc.Category);
			}

			var subCategory = _productSubCategoryRepository.Get(id, false, eagerLoadingProperties.ToArray());

			return Mapper.Map<ProductSubCategory, ProductSubCategoryDto>(subCategory);
		}

		#endregion

		#region Product

		public MessagedResult<bool> AddProduct(ProductDto productDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "商品管理", "添加商品", (ref Product product) =>
			{
				var hospital = _hospitalRepository.Get(productDto.HospitalId, false);

				if (hospital == null)
				{
					throw new HangerdException("医院信息不存在");
				}

				if (_invoicingDomainService.HasSameProductBarcodeUnderHospital(productDto.HospitalId, productDto.Barcode))
				{
					throw new HangerdException("该商品信息已录入");
				}

				product = new Product(hospital, productDto.Name);

				product.Barcode = productDto.Barcode ?? string.Empty;
				product.UnitPrice = productDto.UnitPrice;
				product.UnitName = productDto.UnitName;
				product.QueryTag = productDto.QueryTag;

				product.SetQuantityAlarmConfig(productDto.EnableQuantityAlarm, productDto.QuantityAlarmThreshold);
				product.SetExpiryAlarmConfig(productDto.EnableExpiryAlarm, productDto.ExpiryAlarmThreshold);

				var category = _productCategoryRepository.Get(productDto.Category.Id, true);
				var subCategory = _productSubCategoryRepository.Get(productDto.SubCategory.Id, true);

				product.Categorize(category, subCategory);

				_productRepository.Add(product, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public MessagedResult<bool> UpdateProduct(string productId, ProductDto productDto, IOperator @operator)
		{
			return TryLoggedOperate(@operator, "商品管理", "更新商品", (ref Product product) =>
			{
				product = _productRepository.Get(productId, true, p => p.Category, p => p.SubCategory);

				if (product == null)
				{
					throw new HangerdException("商品信息不存在");
				}

				if (_invoicingDomainService.HasSameProductBarcodeUnderHospital(product.HospitalId, productDto.Barcode, product))
				{
					throw new HangerdException("该商品条码已存在");
				}

				product.Barcode = productDto.Barcode;
				product.Name = productDto.Name;
				product.UnitPrice = productDto.UnitPrice;
				product.UnitName = productDto.UnitName;
				product.QueryTag = productDto.QueryTag;

				product.SetQuantityAlarmConfig(productDto.EnableQuantityAlarm, productDto.QuantityAlarmThreshold);
				product.SetExpiryAlarmConfig(productDto.EnableExpiryAlarm, productDto.ExpiryAlarmThreshold);

				var category = _productCategoryRepository.Get(productDto.Category.Id, true);
				var subCategory = _productSubCategoryRepository.Get(productDto.SubCategory.Id, true);

				product.Categorize(category, subCategory);

				_productRepository.Update(product, true);
				
				UnitOfWork.Commit();

				return true;
			});
		}

		public IEnumerable<ProductDto> GetProducts(string hospitalId, bool loadCategoryAndSubCategory, bool loadInventories, ProductQueryDto queryDto,
			int pageIndex, int pageSize, out int totalNumber)
		{
			var eagerLoadingProperties = new List<Expression<Func<Product, dynamic>>>();

			if (loadCategoryAndSubCategory)
			{
				eagerLoadingProperties.Add(p => p.Category);
				eagerLoadingProperties.Add(p => p.SubCategory);
			}

			if (loadInventories)
			{
				eagerLoadingProperties.Add(p => p.Inventories);
			}

			var spec = ProductSpecifications.HospitalIdEquals(hospitalId);

			if (!string.IsNullOrWhiteSpace(queryDto.Barcode))
			{
				spec &= ProductSpecifications.BarcodeEquals(queryDto.Barcode);
			}

			if (!string.IsNullOrWhiteSpace(queryDto.NameOrTag))
			{
				spec &= (ProductSpecifications.NameContains(queryDto.NameOrTag) | ProductSpecifications.QueryTagEquals(queryDto.NameOrTag));
			}

			var products = _productRepository.GetAll(spec, false, eagerLoadingProperties.ToArray())
				.OrderBy(c => c.CreateTime)
				.Paging(pageIndex, pageSize, out totalNumber);

			return Mapper.Map<IEnumerable<Product>, IEnumerable<ProductDto>>(products);
		}

		public IEnumerable<ProductDto> GetProductsForMembership(string hospitalId, MembershipLevelDto membershipLevelDto, ProductQueryDto queryDto)
		{
			var spec = ProductSpecifications.HospitalIdEquals(hospitalId);

			if (!string.IsNullOrWhiteSpace(queryDto.Barcode))
			{
				spec &= ProductSpecifications.BarcodeEquals(queryDto.Barcode);
			}
			else if (!string.IsNullOrWhiteSpace(queryDto.NameOrTag))
			{
				spec &= (ProductSpecifications.NameContains(queryDto.NameOrTag) | ProductSpecifications.QueryTagEquals(queryDto.NameOrTag));
			}

			var products = _productRepository.GetAll(spec, false, p => p.Inventories)
				.OrderByDescending(p => p.CreateTime)
				.Paging(1, 10);

			return Mapper.Map<IEnumerable<Product>, IEnumerable<ProductDto>>(products);
		}

		public ProductDto GetProduct(string id, bool loadCategoryAndSubCategory, bool loadInventories)
		{
			var eagerLoadingProperties = new List<Expression<Func<Product, dynamic>>>();

			if (loadCategoryAndSubCategory)
			{
				eagerLoadingProperties.Add(p => p.Category);
				eagerLoadingProperties.Add(p => p.SubCategory);
			}

			if (loadInventories)
			{
				eagerLoadingProperties.Add(p => p.Inventories);
			}

			var product = _productRepository.Get(id, false, eagerLoadingProperties.ToArray());

			return Mapper.Map<Product, ProductDto>(product);
		}

		#endregion
	}
}
